package com.xdb.sqlparser.dialect.oracle.parser;

import java.util.List;

import com.xdb.sqlparser.ast.SQLExpr;
import com.xdb.sqlparser.ast.SQLSetQuantifier;
import com.xdb.sqlparser.ast.expr.SQLAggregateExpr;
import com.xdb.sqlparser.ast.expr.SQLBinaryOpExpr;
import com.xdb.sqlparser.ast.expr.SQLBinaryOperator;
import com.xdb.sqlparser.ast.expr.SQLIdentifierExpr;
import com.xdb.sqlparser.ast.expr.SQLListExpr;
import com.xdb.sqlparser.ast.statement.SQLSelect;
import com.xdb.sqlparser.ast.statement.SQLSelectGroupByClause;
import com.xdb.sqlparser.ast.statement.SQLSelectQuery;
import com.xdb.sqlparser.ast.statement.SQLTableSource;
import com.xdb.sqlparser.ast.statement.SQLUnionOperator;
import com.xdb.sqlparser.ast.statement.SQLUnionQuery;
import com.xdb.sqlparser.ast.statement.SQLWithSubqueryClause;
import com.xdb.sqlparser.dialect.oracle.ast.clause.OracleCycleClause;
import com.xdb.sqlparser.dialect.oracle.ast.clause.OracleFlashbackQueryClause;
import com.xdb.sqlparser.dialect.oracle.ast.clause.OracleGroupingSetExpr;
import com.xdb.sqlparser.dialect.oracle.ast.clause.OracleModelClause;
import com.xdb.sqlparser.dialect.oracle.ast.clause.OracleWithSubqueryEntry;
import com.xdb.sqlparser.dialect.oracle.ast.clause.OraclePartitionExtensionClause;
import com.xdb.sqlparser.dialect.oracle.ast.clause.OracleSampleClause;
import com.xdb.sqlparser.dialect.oracle.ast.clause.OracleSearchClause;
import com.xdb.sqlparser.dialect.oracle.ast.clause.OracleFlashbackQueryClause.AsOfFlashbackQueryClause;
import com.xdb.sqlparser.dialect.oracle.ast.clause.OracleFlashbackQueryClause.AsOfSnapshotClause;
import com.xdb.sqlparser.dialect.oracle.ast.clause.OracleFlashbackQueryClause.VersionsFlashbackQueryClause;
import com.xdb.sqlparser.dialect.oracle.ast.clause.OracleModelClause.CellAssignment;
import com.xdb.sqlparser.dialect.oracle.ast.clause.OracleModelClause.CellAssignmentItem;
import com.xdb.sqlparser.dialect.oracle.ast.clause.OracleModelClause.CellReferenceOption;
import com.xdb.sqlparser.dialect.oracle.ast.clause.OracleModelClause.MainModelClause;
import com.xdb.sqlparser.dialect.oracle.ast.clause.OracleModelClause.ModelColumn;
import com.xdb.sqlparser.dialect.oracle.ast.clause.OracleModelClause.ModelColumnClause;
import com.xdb.sqlparser.dialect.oracle.ast.clause.OracleModelClause.ModelRuleOption;
import com.xdb.sqlparser.dialect.oracle.ast.clause.OracleModelClause.ModelRulesClause;
import com.xdb.sqlparser.dialect.oracle.ast.clause.OracleModelClause.QueryPartitionClause;
import com.xdb.sqlparser.dialect.oracle.ast.clause.OracleModelClause.ReferenceModelClause;
import com.xdb.sqlparser.dialect.oracle.ast.clause.OracleModelClause.ReturnRowsClause;
import com.xdb.sqlparser.dialect.oracle.ast.statement.OracleOrderByItem;
import com.xdb.sqlparser.dialect.oracle.ast.statement.OracleSelect;
import com.xdb.sqlparser.dialect.oracle.ast.statement.OracleSelectForUpdate;
import com.xdb.sqlparser.dialect.oracle.ast.statement.OracleSelectHierachicalQueryClause;
import com.xdb.sqlparser.dialect.oracle.ast.statement.OracleSelectJoin;
import com.xdb.sqlparser.dialect.oracle.ast.statement.OracleSelectPivot;
import com.xdb.sqlparser.dialect.oracle.ast.statement.OracleSelectQueryBlock;
import com.xdb.sqlparser.dialect.oracle.ast.statement.OracleSelectRestriction;
import com.xdb.sqlparser.dialect.oracle.ast.statement.OracleSelectSubqueryTableSource;
import com.xdb.sqlparser.dialect.oracle.ast.statement.OracleSelectTableReference;
import com.xdb.sqlparser.dialect.oracle.ast.statement.OracleSelectTableSource;
import com.xdb.sqlparser.dialect.oracle.ast.statement.OracleSelectUnPivot;
import com.xdb.sqlparser.dialect.sql99.parser.SQL99ExprParser;
import com.xdb.sqlparser.dialect.sql99.parser.SQL99SelectParser;
import com.xdb.sqlparser.exception.ParserException;
import com.xdb.sqlparser.parser.SQLToken;

public class OracleSelectParser extends SQL99SelectParser {

	public OracleSelectParser(String sql) {
		super(new OracleExprParser(sql));
	}

	public OracleSelectParser(SQL99ExprParser exprParser) {
		super(exprParser);
	}

	public OracleSelect select() {
		OracleSelect select = new OracleSelect();

		withSubquery(select);

		select.setQuery(query());
		select.setOrderBy(this.parseOrderBy());

		if (lexer.token() == (SQLToken.FOR)) {
			lexer.nextToken();
			accept(SQLToken.UPDATE);

			OracleSelectForUpdate forUpdate = new OracleSelectForUpdate();

			if (lexer.token() == SQLToken.OF) {
				lexer.nextToken();
				this.exprParser.exprList(forUpdate.getOf(), forUpdate);
			}

			if (lexer.token() == SQLToken.NOWAIT) {
				lexer.nextToken();
				forUpdate.setNotWait(true);
			} else if (lexer.token() == SQLToken.WAIT) {
				lexer.nextToken();
				forUpdate.setWait(this.exprParser.primary());
			} else if (identifierEquals("SKIP")) {
				lexer.nextToken();
				acceptIdentifier("LOCKED");
				forUpdate.setSkipLocked(true);
			}

			select.setForUpdate(forUpdate);
		}

		if (select.getOrderBy() == null) {
			select.setOrderBy(this.exprParser.parseOrderBy());
		}

		if (lexer.token() == SQLToken.WITH) {
			lexer.nextToken();

			if (identifierEquals("READ")) {
				lexer.nextToken();

				if (identifierEquals("ONLY")) {
					lexer.nextToken();
				} else {
					throw new ParserException("syntax error");
				}

				select.setRestriction(new OracleSelectRestriction.ReadOnly());
			} else if (lexer.token() == (SQLToken.CHECK)) {
				lexer.nextToken();

				if (identifierEquals("OPTION")) {
					lexer.nextToken();
				} else {
					throw new ParserException("syntax error");
				}

				OracleSelectRestriction.CheckOption checkOption = new OracleSelectRestriction.CheckOption();

				if (lexer.token() == SQLToken.CONSTRAINT) {
					lexer.nextToken();
					throw new ParserException("TODO");
				}

				select.setRestriction(checkOption);
			} else {
				throw new ParserException("syntax error");
			}
		}

		return select;
	}

	protected void withSubquery(SQLSelect select) {
		if (lexer.token() == SQLToken.WITH) {
			lexer.nextToken();

			SQLWithSubqueryClause subqueryFactoringClause = new SQLWithSubqueryClause();
			for (;;) {
				OracleWithSubqueryEntry entry = new OracleWithSubqueryEntry();
				entry.setName((SQLIdentifierExpr) this.exprParser.name());

				if (lexer.token() == SQLToken.LPAREN) {
					lexer.nextToken();
					exprParser.names(entry.getColumns());
					accept(SQLToken.RPAREN);
				}

				accept(SQLToken.AS);
				accept(SQLToken.LPAREN);
				entry.setSubQuery(select());
				accept(SQLToken.RPAREN);

				if (identifierEquals("SEARCH")) {
					lexer.nextToken();
					OracleSearchClause searchClause = new OracleSearchClause();

					if (lexer.token() != SQLToken.IDENTIFIER) {
						throw new ParserException("syntax erorr : " + lexer.token());
					}

					searchClause.setType(OracleSearchClause.Type.valueOf(lexer.stringVal()));
					lexer.nextToken();

					acceptIdentifier("FIRST");
					accept(SQLToken.BY);

					searchClause.getItems().add((OracleOrderByItem) exprParser.parseSelectOrderByItem());

					while (lexer.token() == (SQLToken.COMMA)) {
						lexer.nextToken();
						searchClause.getItems().add((OracleOrderByItem) exprParser.parseSelectOrderByItem());
					}

					accept(SQLToken.SET);

					searchClause.setOrderingColumn((SQLIdentifierExpr) exprParser.name());

					entry.setSearchClause(searchClause);
				}

				if (identifierEquals("CYCLE")) {
					lexer.nextToken();
					OracleCycleClause cycleClause = new OracleCycleClause();
					exprParser.exprList(cycleClause.getAliases(), cycleClause);
					accept(SQLToken.SET);
					cycleClause.setMark(exprParser.expr());
					accept(SQLToken.TO);
					cycleClause.setValue(exprParser.expr());
					accept(SQLToken.DEFAULT);
					cycleClause.setDefaultValue(exprParser.expr());
					entry.setCycleClause(cycleClause);
				}

				subqueryFactoringClause.getEntries().add(entry);

				if (lexer.token() == SQLToken.COMMA) {
					lexer.nextToken();
					continue;
				}

				break;
			}

			select.setWithSubQuery(subqueryFactoringClause);
		}
	}

	public SQLSelectQuery query() {
		if (lexer.token() == (SQLToken.LPAREN)) {
			lexer.nextToken();

			SQLSelectQuery select = query();
			accept(SQLToken.RPAREN);

			return queryRest(select);
		}

		OracleSelectQueryBlock queryBlock = new OracleSelectQueryBlock();
		if (lexer.token() == SQLToken.SELECT) {
			lexer.nextToken();

			if (lexer.token() == SQLToken.COMMENT) {
				lexer.nextToken();
			}

			parseHints(queryBlock);

			if (lexer.token() == (SQLToken.DISTINCT)) {
				queryBlock.setDistionOption(SQLSetQuantifier.DISTINCT);
				lexer.nextToken();
			} else if (lexer.token() == (SQLToken.UNIQUE)) {
				queryBlock.setDistionOption(SQLSetQuantifier.UNIQUE);
				lexer.nextToken();
			} else if (lexer.token() == (SQLToken.ALL)) {
				queryBlock.setDistionOption(SQLSetQuantifier.ALL);
				lexer.nextToken();
			}

			this.exprParser.parseHints(queryBlock.getHints());

			parseSelectList(queryBlock);
		}

		parseInto(queryBlock);

		parseFrom(queryBlock);

		parseWhere(queryBlock);

		parseHierachical(queryBlock);

		parseGroupBy(queryBlock);

		parseModelClause(queryBlock);

		return queryRest(queryBlock);
	}

	public SQLSelectQuery queryRest(SQLSelectQuery selectQuery) {
		if (lexer.token() == (SQLToken.UNION)) {
			SQLUnionQuery union = new SQLUnionQuery();
			union.setLeft(selectQuery);

			lexer.nextToken();

			if (lexer.token() == (SQLToken.ALL)) {
				union.setOperator(SQLUnionOperator.UNION_ALL);
				lexer.nextToken();
			} else if (lexer.token() == (SQLToken.DISTINCT)) {
				union.setOperator(SQLUnionOperator.DISTINCT);
				lexer.nextToken();
			}

			SQLSelectQuery right = query();

			union.setRight(right);

			return queryRest(union);
		}

		if (lexer.token() == SQLToken.INTERSECT) {
			lexer.nextToken();

			SQLUnionQuery union = new SQLUnionQuery();
			union.setLeft(selectQuery);

			union.setOperator(SQLUnionOperator.INTERSECT);

			SQLSelectQuery right = this.query();
			union.setRight(right);

			return union;
		}

		if (lexer.token() == SQLToken.MINUS) {
			lexer.nextToken();

			SQLUnionQuery union = new SQLUnionQuery();
			union.setLeft(selectQuery);

			union.setOperator(SQLUnionOperator.MINUS);

			SQLSelectQuery right = this.query();
			union.setRight(right);

			return union;
		}

		return selectQuery;
	}

	private void parseModelClause(OracleSelectQueryBlock queryBlock) {
		if (lexer.token() != SQLToken.MODEL) {
			return;
		}

		lexer.nextToken();

		OracleModelClause model = new OracleModelClause();
		parseCellReferenceOptions(model.getCellReferenceOptions());

		if (identifierEquals("RETURN")) {
			lexer.nextToken();
			ReturnRowsClause returnRowsClause = new ReturnRowsClause();
			if (lexer.token() == SQLToken.ALL) {
				lexer.nextToken();
				returnRowsClause.setAll(true);
			} else {
				acceptIdentifier("UPDATED");
			}
			acceptIdentifier("ROWS");

			model.setReturnRowsClause(returnRowsClause);
		}

		while (identifierEquals("REFERENCE")) {
			ReferenceModelClause referenceModelClause = new ReferenceModelClause();
			lexer.nextToken();

			SQLExpr name = expr();
			referenceModelClause.setName(name);

			accept(SQLToken.ON);
			accept(SQLToken.LPAREN);
			OracleSelect subQuery = this.select();
			accept(SQLToken.RPAREN);
			referenceModelClause.setSubQuery(subQuery);

			parseModelColumnClause(referenceModelClause);

			parseCellReferenceOptions(referenceModelClause.getCellReferenceOptions());

			model.getReferenceModelClauses().add(referenceModelClause);
		}

		parseMainModelClause(model);

		queryBlock.setModelClause(model);
	}

	private void parseMainModelClause(OracleModelClause modelClause) {
		MainModelClause mainModel = new MainModelClause();

		if (identifierEquals("MAIN")) {
			lexer.nextToken();
			mainModel.setMainModelName(expr());
		}

		ModelColumnClause modelColumnClause = new ModelColumnClause();
		parseQueryPartitionClause(modelColumnClause);
		mainModel.setModelColumnClause(modelColumnClause);

		acceptIdentifier("DIMENSION");
		accept(SQLToken.BY);
		accept(SQLToken.LPAREN);
		for (;;) {
			if (lexer.token() == SQLToken.RPAREN) {
				lexer.nextToken();
				break;
			}

			ModelColumn column = new ModelColumn();
			column.setExpr(expr());
			column.setAlias(as());
			modelColumnClause.getDimensionByColumns().add(column);

			if (lexer.token() == SQLToken.COMMA) {
				lexer.nextToken();
				continue;
			}
		}

		acceptIdentifier("MEASURES");
		accept(SQLToken.LPAREN);
		for (;;) {
			if (lexer.token() == SQLToken.RPAREN) {
				lexer.nextToken();
				break;
			}

			ModelColumn column = new ModelColumn();
			column.setExpr(expr());
			column.setAlias(as());
			modelColumnClause.getMeasuresColumns().add(column);

			if (lexer.token() == SQLToken.COMMA) {
				lexer.nextToken();
				continue;
			}
		}
		mainModel.setModelColumnClause(modelColumnClause);

		parseCellReferenceOptions(mainModel.getCellReferenceOptions());

		parseModelRulesClause(mainModel);

		modelClause.setMainModel(mainModel);
	}

	private void parseModelRulesClause(MainModelClause mainModel) {
		ModelRulesClause modelRulesClause = new ModelRulesClause();
		if (identifierEquals("RULES")) {
			lexer.nextToken();
			if (lexer.token() == SQLToken.UPDATE) {
				modelRulesClause.getOptions().add(ModelRuleOption.UPDATE);
				lexer.nextToken();
			} else if (identifierEquals("UPSERT")) {
				modelRulesClause.getOptions().add(ModelRuleOption.UPSERT);
				lexer.nextToken();
			}

			if (identifierEquals("AUTOMATIC")) {
				lexer.nextToken();
				accept(SQLToken.ORDER);
				modelRulesClause.getOptions().add(ModelRuleOption.AUTOMATIC_ORDER);
			} else if (identifierEquals("SEQUENTIAL")) {
				lexer.nextToken();
				accept(SQLToken.ORDER);
				modelRulesClause.getOptions().add(ModelRuleOption.SEQUENTIAL_ORDER);
			}
		}

		if (identifierEquals("ITERATE")) {
			lexer.nextToken();
			accept(SQLToken.LPAREN);
			modelRulesClause.setIterate(expr());
			accept(SQLToken.RPAREN);

			if (identifierEquals("UNTIL")) {
				lexer.nextToken();
				accept(SQLToken.LPAREN);
				modelRulesClause.setUntil(expr());
				accept(SQLToken.RPAREN);
			}
		}

		accept(SQLToken.LPAREN);
		for (;;) {
			if (lexer.token() == SQLToken.RPAREN) {
				lexer.nextToken();
				break;
			}

			CellAssignmentItem item = new CellAssignmentItem();
			if (lexer.token() == SQLToken.UPDATE) {
				item.setOption(ModelRuleOption.UPDATE);
			} else if (identifierEquals("UPSERT")) {
				item.setOption(ModelRuleOption.UPSERT);
			}

			item.setCellAssignment(parseCellAssignment());
			item.setOrderBy(this.parseOrderBy());
			accept(SQLToken.EQ);
			item.setExpr(expr());

			modelRulesClause.getCellAssignmentItems().add(item);
		}

		mainModel.setModelRulesClause(modelRulesClause);
	}

	private CellAssignment parseCellAssignment() {
		CellAssignment cellAssignment = new CellAssignment();

		cellAssignment.setMeasureColumn(expr());
		accept(SQLToken.LBRACKET);
		this.exprParser.exprList(cellAssignment.getConditions(), cellAssignment);
		accept(SQLToken.RBRACKET);

		return cellAssignment;
	}

	private void parseQueryPartitionClause(ModelColumnClause modelColumnClause) {
		if (identifierEquals("PARTITION")) {
			QueryPartitionClause queryPartitionClause = new QueryPartitionClause();

			lexer.nextToken();
			accept(SQLToken.BY);
			if (lexer.token() == SQLToken.LPAREN) {
				lexer.nextToken();
				exprParser.exprList(queryPartitionClause.getExprList(), queryPartitionClause);
				accept(SQLToken.RPAREN);
			} else {
				exprParser.exprList(queryPartitionClause.getExprList(), queryPartitionClause);
			}
			modelColumnClause.setQueryPartitionClause(queryPartitionClause);
		}
	}

	private void parseModelColumnClause(ReferenceModelClause referenceModelClause) {
		throw new ParserException();
	}

	private void parseCellReferenceOptions(List<CellReferenceOption> options) {
		if (identifierEquals("IGNORE")) {
			lexer.nextToken();
			acceptIdentifier("NAV");
			options.add(CellReferenceOption.IgnoreNav);
		} else if (identifierEquals("KEEP")) {
			lexer.nextToken();
			acceptIdentifier("NAV");
			options.add(CellReferenceOption.KeepNav);
		}

		if (lexer.token() == SQLToken.UNIQUE) {
			lexer.nextToken();
			if (identifierEquals("DIMENSION")) {
				lexer.nextToken();
				options.add(CellReferenceOption.UniqueDimension);
			} else {
				acceptIdentifier("SINGLE");
				acceptIdentifier("REFERENCE");
				options.add(CellReferenceOption.UniqueDimension);
			}
		}
	}

	private void parseGroupBy(OracleSelectQueryBlock queryBlock) {
		if (lexer.token() == (SQLToken.GROUP)) {
			lexer.nextToken();
			accept(SQLToken.BY);

			SQLSelectGroupByClause groupBy = new SQLSelectGroupByClause();
			for (;;) {
				if (identifierEquals("GROUPING")) {
					OracleGroupingSetExpr groupingSet = new OracleGroupingSetExpr();
					lexer.nextToken();
					acceptIdentifier("SETS");
					accept(SQLToken.LPAREN);
					exprParser.exprList(groupingSet.getParameters(), groupingSet);
					accept(SQLToken.RPAREN);
					groupBy.addItem(groupingSet);
				} else {
					groupBy.addItem(this.exprParser.expr());
				}

				if (!(lexer.token() == (SQLToken.COMMA))) {
					break;
				}

				lexer.nextToken();
			}

			if (lexer.token() == (SQLToken.HAVING)) {
				lexer.nextToken();

				groupBy.setHaving(this.exprParser.expr());
			}

			queryBlock.setGroupBy(groupBy);
		} else if (lexer.token() == (SQLToken.HAVING)) {
			lexer.nextToken();

			SQLSelectGroupByClause groupBy = new SQLSelectGroupByClause();
			groupBy.setHaving(this.exprParser.expr());

			if (lexer.token() == (SQLToken.GROUP)) {
				lexer.nextToken();
				accept(SQLToken.BY);

				for (;;) {
					if (identifierEquals("GROUPING")) {
						OracleGroupingSetExpr groupingSet = new OracleGroupingSetExpr();
						lexer.nextToken();
						acceptIdentifier("SETS");
						accept(SQLToken.LPAREN);
						exprParser.exprList(groupingSet.getParameters(), groupingSet);
						accept(SQLToken.RPAREN);
						groupBy.addItem(groupingSet);
					} else {
						groupBy.addItem(this.exprParser.expr());
					}

					if (!(lexer.token() == (SQLToken.COMMA))) {
						break;
					}

					lexer.nextToken();
				}
			}

			queryBlock.setGroupBy(groupBy);
		}
	}

	protected String as() {
		if (lexer.token() == SQLToken.CONNECT) {
			return null;
		}

		return super.as();
	}

	private void parseHierachical(OracleSelectQueryBlock queryBlock) {
		OracleSelectHierachicalQueryClause hierachical = null;

		if (lexer.token() == SQLToken.CONNECT) {
			hierachical = new OracleSelectHierachicalQueryClause();
			lexer.nextToken();
			accept(SQLToken.BY);

			if (lexer.token() == SQLToken.PRIOR) {
				lexer.nextToken();
				hierachical.setPrior(true);
			}

			if (identifierEquals("NOCYCLE")) {
				hierachical.setNoCycle(true);
				lexer.nextToken();

				if (lexer.token() == SQLToken.PRIOR) {
					lexer.nextToken();
					hierachical.setPrior(true);
				}
			}
			hierachical.setConnectBy(this.exprParser.expr());
		}

		if (lexer.token() == SQLToken.START) {
			lexer.nextToken();
			if (hierachical == null) {
				hierachical = new OracleSelectHierachicalQueryClause();
			}
			accept(SQLToken.WITH);

			hierachical.setStartWith(this.exprParser.expr());
		}

		if (lexer.token() == SQLToken.CONNECT) {
			if (hierachical == null) {
				hierachical = new OracleSelectHierachicalQueryClause();
			}

			lexer.nextToken();
			accept(SQLToken.BY);

			if (lexer.token() == SQLToken.PRIOR) {
				lexer.nextToken();
				hierachical.setPrior(true);
			}

			if (identifierEquals("NOCYCLE")) {
				hierachical.setNoCycle(true);
				lexer.nextToken();

				if (lexer.token() == SQLToken.PRIOR) {
					lexer.nextToken();
					hierachical.setPrior(true);
				}
			}
			hierachical.setConnectBy(this.exprParser.expr());
		}

		if (hierachical != null) {
			queryBlock.setHierachicalQueryClause(hierachical);
		}
	}

	@Override
	public SQLTableSource parseTableSource() {
		if (lexer.token() == (SQLToken.LPAREN)) {
			lexer.nextToken();
			OracleSelectSubqueryTableSource tableSource;
			if (lexer.token() == SQLToken.SELECT || lexer.token() == SQLToken.WITH) {
				tableSource = new OracleSelectSubqueryTableSource(select());
			} else if (lexer.token() == (SQLToken.LPAREN)) {
				tableSource = new OracleSelectSubqueryTableSource(select());
			} else {
				throw new ParserException("TODO :" + lexer.token());
			}
			accept(SQLToken.RPAREN);

			parsePivot((OracleSelectTableSource) tableSource);

			return parseTableSourceRest(tableSource);
		}

		if (lexer.token() == (SQLToken.SELECT)) {
			throw new ParserException("TODO");
		}

		OracleSelectTableReference tableReference = new OracleSelectTableReference();

		if (identifierEquals("ONLY")) {
			lexer.nextToken();
			tableReference.setOnly(true);
			accept(SQLToken.LPAREN);
			parseTableSourceQueryTableExpr(tableReference);
			accept(SQLToken.RPAREN);
		} else {
			parseTableSourceQueryTableExpr(tableReference);
			parsePivot(tableReference);
		}

		return parseTableSourceRest(tableReference);
	}

	private void parseTableSourceQueryTableExpr(OracleSelectTableReference tableReference) {
		tableReference.setExpr(this.exprParser.expr());

		{
			OracleFlashbackQueryClause clause = flashback();
			tableReference.setFlashback(clause);
		}

		if (identifierEquals("SAMPLE")) {
			lexer.nextToken();

			OracleSampleClause sample = new OracleSampleClause();

			if (identifierEquals("BLOCK")) {
				sample.setBlock(true);
				lexer.nextToken();
			}

			accept(SQLToken.LPAREN);
			this.exprParser.exprList(sample.getPercent(), sample);
			accept(SQLToken.RPAREN);

			if (identifierEquals("SEED")) {
				lexer.nextToken();
				accept(SQLToken.LPAREN);
				sample.setSeedValue(expr());
				accept(SQLToken.RPAREN);
			}

			tableReference.setSampleClause(sample);
		}

		if (identifierEquals("PARTITION")) {
			lexer.nextToken();
			OraclePartitionExtensionClause partition = new OraclePartitionExtensionClause();

			if (lexer.token() == SQLToken.LPAREN) {
				lexer.nextToken();
				partition.setPartition(exprParser.name());
				accept(SQLToken.RPAREN);
			} else {
				accept(SQLToken.FOR);
				accept(SQLToken.LPAREN);
				exprParser.names(partition.getFor());
				accept(SQLToken.RPAREN);
			}

			tableReference.setPartition(partition);
		}

		if (identifierEquals("SUBPARTITION")) {
			lexer.nextToken();
			OraclePartitionExtensionClause partition = new OraclePartitionExtensionClause();
			partition.setSubPartition(true);

			if (lexer.token() == SQLToken.LPAREN) {
				lexer.nextToken();
				partition.setPartition(exprParser.name());
				accept(SQLToken.RPAREN);
			} else {
				accept(SQLToken.FOR);
				accept(SQLToken.LPAREN);
				exprParser.names(partition.getFor());
				accept(SQLToken.RPAREN);
			}

			tableReference.setPartition(partition);
		}

		if (identifierEquals("VERSIONS")) {
			lexer.nextToken();

			if (lexer.token() == SQLToken.BETWEEN) {
				lexer.nextToken();

				VersionsFlashbackQueryClause clause = new VersionsFlashbackQueryClause();
				if (identifierEquals("SCN")) {
					clause.setType(AsOfFlashbackQueryClause.Type.SCN);
					lexer.nextToken();
				} else {
					acceptIdentifier("TIMESTAMP");
					clause.setType(AsOfFlashbackQueryClause.Type.TIMESTAMP);
				}

				SQLBinaryOpExpr binaryExpr = (SQLBinaryOpExpr) exprParser.expr();
				if (binaryExpr.getOperator() != SQLBinaryOperator.BooleanAnd) {
					throw new ParserException("syntax error : " + binaryExpr.getOperator());
				}

				clause.setBegin(binaryExpr.getLeft());
				clause.setEnd(binaryExpr.getRight());

				tableReference.setFlashback(clause);
			} else {
				throw new ParserException("TODO");
			}
		}

	}

	private OracleFlashbackQueryClause flashback() {
		if (lexer.token() == SQLToken.AS) {
			lexer.nextToken();
		}

		if (lexer.token() == SQLToken.OF) {
			lexer.nextToken();

			if (identifierEquals("SCN")) {
				AsOfFlashbackQueryClause clause = new AsOfFlashbackQueryClause();
				clause.setType(AsOfFlashbackQueryClause.Type.SCN);
				lexer.nextToken();
				clause.setExpr(exprParser.expr());

				return clause;
			} else if (identifierEquals("SNAPSHOT")) {
				lexer.nextToken();
				accept(SQLToken.LPAREN);
				AsOfSnapshotClause clause = new AsOfSnapshotClause();
				clause.setExpr(this.expr());
				accept(SQLToken.RPAREN);

				return clause;
			} else {
				AsOfFlashbackQueryClause clause = new AsOfFlashbackQueryClause();
				acceptIdentifier("TIMESTAMP");
				clause.setType(AsOfFlashbackQueryClause.Type.TIMESTAMP);
				clause.setExpr(exprParser.expr());

				return clause;
			}

		}

		return null;
	}

	protected SQLTableSource parseTableSourceRest(OracleSelectTableSource tableSource) {
		if (lexer.token() == SQLToken.AS) {
			lexer.nextToken();

			if (lexer.token() == SQLToken.OF) {
				tableSource.setFlashback(flashback());
			}

			tableSource.setAlias(as());
		} else if ((tableSource.getAlias() == null) || (tableSource.getAlias().length() == 0)) {
			if (lexer.token() != SQLToken.LEFT && lexer.token() != SQLToken.RIGHT && lexer.token() != SQLToken.FULL) {
				tableSource.setAlias(as());
			}
		}

		if (lexer.token() == SQLToken.HINT) {
			this.exprParser.parseHints(tableSource.getHints());
		}

		OracleSelectJoin.JoinType joinType = null;

		if (lexer.token() == SQLToken.LEFT) {
			lexer.nextToken();
			if (lexer.token() == SQLToken.OUTER) {
				lexer.nextToken();
			}
			accept(SQLToken.JOIN);
			joinType = OracleSelectJoin.JoinType.LEFT_OUTER_JOIN;
		}

		if (lexer.token() == SQLToken.RIGHT) {
			lexer.nextToken();
			if (lexer.token() == SQLToken.OUTER) {
				lexer.nextToken();
			}
			accept(SQLToken.JOIN);
			joinType = OracleSelectJoin.JoinType.RIGHT_OUTER_JOIN;
		}

		if (lexer.token() == SQLToken.FULL) {
			lexer.nextToken();
			if (lexer.token() == SQLToken.OUTER) {
				lexer.nextToken();
			}
			accept(SQLToken.JOIN);
			joinType = OracleSelectJoin.JoinType.FULL_OUTER_JOIN;
		}

		if (lexer.token() == SQLToken.INNER) {
			lexer.nextToken();
			accept(SQLToken.JOIN);
			joinType = OracleSelectJoin.JoinType.INNER_JOIN;
		}
		if (lexer.token() == SQLToken.CROSS) {
			lexer.nextToken();
			accept(SQLToken.JOIN);
			joinType = OracleSelectJoin.JoinType.CROSS_JOIN;
		}

		if (lexer.token() == SQLToken.JOIN) {
			lexer.nextToken();
			joinType = OracleSelectJoin.JoinType.JOIN;
		}

		if (lexer.token() == (SQLToken.COMMA)) {
			lexer.nextToken();
			joinType = OracleSelectJoin.JoinType.COMMA;
		}

		if (joinType != null) {
			OracleSelectJoin join = new OracleSelectJoin();
			join.setLeft(tableSource);
			join.setJoinType(joinType);
			join.setRight(parseTableSource());

			if (lexer.token() == SQLToken.ON) {
				lexer.nextToken();
				join.setCondition(this.exprParser.expr());
			} else if (lexer.token() == SQLToken.USING) {
				lexer.nextToken();
				accept(SQLToken.LPAREN);
				this.exprParser.exprList(join.getUsing(), join);
				accept(SQLToken.RPAREN);
			}

			return parseTableSourceRest(join);
		}

		return tableSource;
	}

	private void parsePivot(OracleSelectTableSource tableSource) {
		OracleSelectPivot.Item item;
		if (identifierEquals("PIVOT")) {
			lexer.nextToken();

			OracleSelectPivot pivot = new OracleSelectPivot();

			if (identifierEquals("XML")) {
				lexer.nextToken();
				pivot.setXml(true);
			}

			accept(SQLToken.LPAREN);
			while (true) {
				item = new OracleSelectPivot.Item();
				item.setExpr((SQLAggregateExpr) this.exprParser.expr());
				item.setAlias(as());
				pivot.getItems().add(item);

				if (!(lexer.token() == (SQLToken.COMMA))) {
					break;
				}
				lexer.nextToken();
			}

			accept(SQLToken.FOR);

			if (lexer.token() == (SQLToken.LPAREN)) {
				lexer.nextToken();
				while (true) {
					pivot.getPivotFor().add(new SQLIdentifierExpr(lexer.stringVal()));
					lexer.nextToken();

					if (!(lexer.token() == (SQLToken.COMMA))) {
						break;
					}
					lexer.nextToken();
				}

				accept(SQLToken.RPAREN);
			} else {
				pivot.getPivotFor().add(new SQLIdentifierExpr(lexer.stringVal()));
				lexer.nextToken();
			}

			accept(SQLToken.IN);
			accept(SQLToken.LPAREN);
			if (lexer.token() == (SQLToken.LPAREN)) {
				throw new ParserException("TODO");
			}

			if (lexer.token() == (SQLToken.SELECT)) {
				throw new ParserException("TODO");
			}

			for (;;) {
				item = new OracleSelectPivot.Item();
				item.setExpr(this.exprParser.expr());
				item.setAlias(as());
				pivot.getPivotIn().add(item);

				if (lexer.token() != SQLToken.COMMA) {
					break;
				}

				lexer.nextToken();
			}

			accept(SQLToken.RPAREN);

			accept(SQLToken.RPAREN);

			tableSource.setPivot(pivot);
		} else if (identifierEquals("UNPIVOT")) {
			lexer.nextToken();

			OracleSelectUnPivot unPivot = new OracleSelectUnPivot();
			if (identifierEquals("INCLUDE")) {
				lexer.nextToken();
				acceptIdentifier("NULLS");
				unPivot.setNullsIncludeType(OracleSelectUnPivot.NullsIncludeType.INCLUDE_NULLS);
			} else if (identifierEquals("EXCLUDE")) {
				lexer.nextToken();
				acceptIdentifier("NULLS");
				unPivot.setNullsIncludeType(OracleSelectUnPivot.NullsIncludeType.EXCLUDE_NULLS);
			}

			accept(SQLToken.LPAREN);

			if (lexer.token() == (SQLToken.LPAREN)) {
				lexer.nextToken();
				this.exprParser.exprList(unPivot.getItems(), unPivot);
				accept(SQLToken.RPAREN);
			} else {
				unPivot.getItems().add(this.exprParser.expr());
			}

			accept(SQLToken.FOR);

			if (lexer.token() == (SQLToken.LPAREN)) {
				lexer.nextToken();
				while (true) {
					unPivot.getPivotFor().add(new SQLIdentifierExpr(lexer.stringVal()));
					lexer.nextToken();

					if (!(lexer.token() == (SQLToken.COMMA))) {
						break;
					}
					lexer.nextToken();
				}

				accept(SQLToken.RPAREN);
			} else {
				unPivot.getPivotFor().add(new SQLIdentifierExpr(lexer.stringVal()));
				lexer.nextToken();
			}

			accept(SQLToken.IN);
			accept(SQLToken.LPAREN);
			if (lexer.token() == (SQLToken.LPAREN)) {
				throw new ParserException("TODO");
			}

			if (lexer.token() == (SQLToken.SELECT)) {
				throw new ParserException("TODO");
			}

			for (;;) {
				item = new OracleSelectPivot.Item();
				item.setExpr(this.exprParser.expr());
				item.setAlias(as());
				unPivot.getPivotIn().add(item);

				if (lexer.token() != SQLToken.COMMA) {
					break;
				}

				lexer.nextToken();
			}

			accept(SQLToken.RPAREN);

			accept(SQLToken.RPAREN);

			tableSource.setPivot(unPivot);
		}
	}

	protected void parseInto(OracleSelectQueryBlock x) {
		if (lexer.token() == SQLToken.INTO) {
			lexer.nextToken();
			SQLExpr expr = expr();
			if (lexer.token() != SQLToken.COMMA) {
				x.setInto(expr);
				return;
			}
			SQLListExpr list = new SQLListExpr();
			list.getItems().add(expr);
			while (lexer.token() == SQLToken.COMMA) {
				lexer.nextToken();
				list.getItems().add(expr());
			}
			x.setInto(list);
		}
	}

	private void parseHints(OracleSelectQueryBlock queryBlock) {
		this.exprParser.parseHints(queryBlock.getHints());
	}
}
